#!/bin/bash

# =============================================================================
# 自动化集成测试脚本
# 用于Jenkins CI/CD流水线，启动项目并执行集成测试
# =============================================================================

set -e  # 遇到错误立即退出

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# Docker Compose 命令探测
detect_compose_command() {
    if command -v docker-compose >/dev/null 2>&1; then
        if docker-compose --version >/dev/null 2>&1; then
            echo "docker-compose"
            return 0
        fi
    fi
    
    if command -v docker >/dev/null 2>&1; then
        if docker compose version >/dev/null 2>&1; then
            echo "docker compose"
            return 0
        fi
    fi
    
    log_error "Neither 'docker-compose' nor 'docker compose' command is available"
    log_error "Please install Docker Compose v1 or Docker with Compose v2 plugin"
    return 1
}

# 设置 Docker Compose 命令
COMPOSE_CMD=$(detect_compose_command)
if [ $? -ne 0 ]; then
    exit 1
fi
log_info "Using Docker Compose command: $COMPOSE_CMD"

# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DEPLOY_DIR="$PROJECT_ROOT/depoy"
TESTS_DIR="$PROJECT_ROOT/integration-tests"

log_info "项目根目录: $PROJECT_ROOT"
log_info "部署目录: $DEPLOY_DIR"
log_info "测试目录: $TESTS_DIR"

# 加载环境变量
load_env_vars() {
    log_info "加载环境变量..."
    
    # 加载部署环境变量
    if [ -f "$DEPLOY_DIR/.env" ]; then
        log_info "加载部署环境变量: $DEPLOY_DIR/.env"
        set -a  # 自动导出变量
        source "$DEPLOY_DIR/.env"
        set +a  # 关闭自动导出
    fi
    
    # 加载测试环境变量
    if [ -f "$TESTS_DIR/.env" ]; then
        log_info "加载测试环境变量: $TESTS_DIR/.env"
        set -a  # 自动导出变量
        source "$TESTS_DIR/.env"
        set +a  # 关闭自动导出
    fi
    
    # 验证关键环境变量
    if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
        log_error "MYSQL_ROOT_PASSWORD 环境变量未设置"
        exit 1
    fi
    
    if [ -z "$MYSQL_DATABASE" ]; then
        log_error "MYSQL_DATABASE 环境变量未设置"
        exit 1
    fi
    
    if [ -z "$DB_PASSWORD" ]; then
        log_error "DB_PASSWORD 环境变量未设置"
        exit 1
    fi
    
    log_success "环境变量加载完成"
    log_info "数据库配置: DB_HOST=${DB_HOST:-localhost}, DB_PORT=${DB_PORT:-3306}, DB_DATABASE=${DB_DATABASE}, DB_USERNAME=${DB_USERNAME:-root}"
}

# 检查必要文件
check_files() {
    log_info "检查必要文件..."
    
    if [ ! -f "$DEPLOY_DIR/docker-compose.dev.yml" ]; then
        log_error "docker-compose.dev.yml 文件不存在"
        exit 1
    fi
    
    if [ ! -f "$DEPLOY_DIR/.env" ]; then
        log_error "deploy/.env 文件不存在"
        exit 1
    fi
    
    if [ ! -f "$TESTS_DIR/.env" ]; then
        log_error "integration-tests/.env 文件不存在"
        exit 1
    fi
    
    if [ ! -f "$TESTS_DIR/requirements.txt" ]; then
        log_error "requirements.txt 文件不存在"
        exit 1
    fi
    
    log_success "所有必要文件检查通过"
}

# 清理函数 - 临时注释掉以便查看报错信息
cleanup() {
    cd "$DEPLOY_DIR"
    
    # 停止并删除容器 --volumes删除数据卷 --remove-orphans删除孤儿容器
    $COMPOSE_CMD -f docker-compose.dev.yml down --volumes --remove-orphans || true
    
    # 清理测试容器
    docker rm -f pytest_runner 2>/dev/null || true
    
    # 清理未使用的镜像和网络
    docker system prune -f || true
    
}

# 等待服务就绪
wait_for_service() {
    local service_name=$1
    local max_attempts=60
    local attempt=1
    
    log_info "等待 $service_name 服务就绪..."
    
    while [ $attempt -le $max_attempts ]; do
        # 检查容器是否在运行
        if ! $COMPOSE_CMD -f docker-compose.dev.yml ps $service_name | grep -q "Up"; then
            log_warning "$service_name 容器未运行，等待启动..."
            sleep 5
            attempt=$((attempt + 1))
            continue
        fi
        
        # 根据服务类型进行特定的健康检查
        case $service_name in
            mysql)
                if $COMPOSE_CMD -f docker-compose.dev.yml exec -T $service_name mysqladmin ping -h localhost -uroot -p"${MYSQL_ROOT_PASSWORD}" --silent >/dev/null 2>&1; then
                    log_success "$service_name 服务已就绪"
                    return 0
                fi
                ;;
            redis)
                if $COMPOSE_CMD -f docker-compose.dev.yml exec -T $service_name redis-cli -a "${REDIS_PASSWORD}" ping >/dev/null 2>&1; then
                    log_success "$service_name 服务已就绪"
                    return 0
                fi
                ;;
            rabbitmq)
                if $COMPOSE_CMD -f docker-compose.dev.yml exec -T $service_name rabbitmq-diagnostics -q ping >/dev/null 2>&1; then
                    log_success "$service_name 服务已就绪"
                    return 0
                fi
                ;;
            *)
                # 默认检查：容器能执行命令即认为就绪
                if $COMPOSE_CMD -f docker-compose.dev.yml exec -T $service_name echo "Service is ready" >/dev/null 2>&1; then
                    log_success "$service_name 服务已就绪"
                    return 0
                fi
                ;;
        esac
        
        log_info "等待 $service_name 服务启动... ($attempt/$max_attempts)"
        sleep 5
        attempt=$((attempt + 1))
    done
    
    log_error "$service_name 服务启动超时"
    # 输出容器日志以便调试
    log_info "输出 $service_name 容器日志："
    $COMPOSE_CMD -f docker-compose.dev.yml logs --tail=50 $service_name || true
    return 1
}

# 等待应用健康检查
wait_for_app_health() {
    local max_attempts=30
    local attempt=1
    
    log_info "等待应用健康检查..."
    
    while [ $attempt -le $max_attempts ]; do
        # 检查应用是否响应 - 获取HTTP状态码
        local health_status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/health 2>/dev/null || echo "000")
        
        # 200=成功, 401=未授权, 403=禁止访问 - 都表示应用已启动
        if [ "$health_status" = "200" ] || [ "$health_status" = "401" ] || [ "$health_status" = "403" ]; then
            log_success "应用健康检查通过 (HTTP状态码: $health_status)"
            return 0
        fi
        
        # 如果健康检查端点不可用，检查根路径
        local root_status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080 2>/dev/null || echo "000")
        
        # 同样，200/401/403都表示应用已启动
        if [ "$root_status" = "200" ] || [ "$root_status" = "401" ] || [ "$root_status" = "403" ]; then
            log_success "应用基本连接检查通过 (HTTP状态码: $root_status)"
            return 0
        fi
        
        log_info "等待应用启动... ($attempt/$max_attempts) [健康检查状态码: $health_status, 根路径状态码: $root_status]"
        sleep 10
        attempt=$((attempt + 1))
    done
    
    log_error "应用启动超时"
    return 1
}

# 启动依赖服务
start_services() {
    log_info "启动依赖服务..."
    cd "$DEPLOY_DIR"
    
    # 拉取最新镜像
    $COMPOSE_CMD -f docker-compose.dev.yml pull || true
    
    # 启动所有服务
    $COMPOSE_CMD -f docker-compose.dev.yml up -d
    
    # 等待各服务就绪
    wait_for_service mysql
    wait_for_service redis
    wait_for_service rabbitmq
    
    # 等待应用启动
    wait_for_app_health
    
    log_success "所有服务启动完成"
}

# 运行集成测试
run_tests() {
    log_info "开始运行集成测试..."
    
    # 创建测试结果目录
    mkdir -p "$TESTS_DIR/allure-results"
    
    # 设置测试环境变量
    export API_BASE_URL="${API_BASE_URL:-http://host.docker.internal:8080}"
    
    # 构建测试镜像
    log_info "构建测试环境..."
    cat > "$TESTS_DIR/Dockerfile.test" << 'EOF'
FROM python:3.9-slim

WORKDIR /tests

# 设置pip环境变量优化性能  关闭检查更新 和 关闭进度条显示
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
ENV PIP_PROGRESS_BAR=off

# 先复制requirements.txt以利用Docker层缓存
COPY requirements.txt .

# 安装Python依赖（使用BuildKit缓存挂载加速重复构建）
# --mount缓存挂在类型指定为cache，把pip包放到docker的对应目录下，后续构建需要的话，直接用这里的
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt -i https://pypi.mirrors.ustc.edu.cn/simple/

# 复制其余测试文件（代码变更不会影响依赖层缓存）
#宿主机：容器路径
COPY . .

# 设置环境变量
ENV PYTHONPATH=/tests
ENV API_BASE_URL=http://host.docker.internal:8080
ENV PYTHONIOENCODING=utf-8
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8

# 失败的时候显示简短的栈信息
CMD ["pytest", "--alluredir=allure-results", "-v", "--tb=short", "--ignore=test_env_config.py"]
EOF
    
    # 构建测试镜像（启用BuildKit以支持缓存挂载）
    cd "$TESTS_DIR"
    DOCKER_BUILDKIT=1 docker build -f Dockerfile.test -t pytest_runner .
    
    # 执行pytest测试用例（在 set -e 下安全捕获退出码）
    log_info "执行pytest测试用例..."
    if docker run --rm \
        --name pytest_runner \
        --network host \
        -v "$TESTS_DIR/allure-results:/tests/allure-results" \
        -e API_BASE_URL="${API_BASE_URL:-http://localhost:8080}" \
        -e DB_HOST="${DB_HOST:-localhost}" \
        -e DB_PORT="${DB_PORT:-3306}" \
        -e DB_USERNAME="${DB_USERNAME:-root}" \
        -e DB_PASSWORD="${DB_PASSWORD}" \
        -e DB_DATABASE="${DB_DATABASE}" \
        -e REDIS_HOST="${REDIS_HOST:-localhost}" \
        -e REDIS_PORT="${REDIS_PORT:-6379}" \
        -e REDIS_PASSWORD="${REDIS_PASSWORD}" \
        pytest_runner; then
        TESTS_EXIT_CODE=0
        log_success "pytest容器执行成功"
    else
        TESTS_EXIT_CODE=$?
        log_error "pytest容器执行失败，退出码: $TESTS_EXIT_CODE"
    fi
    
    # 清理测试镜像
    docker rmi pytest_runner 2>/dev/null || true
    rm -f "$TESTS_DIR/Dockerfile.test"
    
    log_info "测试执行结束，结果已保存到 allure-results 目录"
}

# 基于Allure结果计算通过率并按阈值决定最终构建结果
evaluate_pass_rate() {
    local results_dir="$TESTS_DIR/allure-results"
    local threshold="${PASS_RATE_THRESHOLD:-50}"  # 可通过环境变量覆盖，默认 50%
    
    if [ ! -d "$results_dir" ]; then
        log_error "未找到测试结果目录: $results_dir"
        return 1
    fi

    # 统计 passed/failed/broken（跳过 skipped）
    local passed failed broken total pass_rate_int
    passed=$(find "$results_dir" -name "*-result.json" -print0 | xargs -0 grep -h "\"status\": \"passed\"" | wc -l)
    failed=$(find "$results_dir" -name "*-result.json" -print0 | xargs -0 grep -h "\"status\": \"failed\"" | wc -l)
    broken=$(find "$results_dir" -name "*-result.json" -print0 | xargs -0 grep -h "\"status\": \"broken\"" | wc -l)
    total=$((passed + failed + broken))

    if [ "$total" -eq 0 ]; then
        log_error "未统计到有效的测试结果（total=0），无法计算通过率"
        return 1
    fi

    # 计算百分比（四舍五入为整数）
    pass_rate_int=$(awk -v p="$passed" -v t="$total" 'BEGIN{ printf("%.0f", (p/t)*100) }')
    log_info "测试统计：passed=$passed, failed=$failed, broken=$broken, total=$total"
    log_info "测试通过率：$pass_rate_int%（阈值=${threshold}%）"
    
    # 优先级策略：
    # 1) pytest退出码为0则直接成功
    # 2) 退出码非0，但通过率>=阈值则视为成功（但打印警告）
    # 3) 否则失败
    if [ "${TESTS_EXIT_CODE:-1}" -eq 0 ]; then
        log_success "pytest退出码为0，构建标记为成功"
        return 0
    fi
    
    if [ "$pass_rate_int" -ge "$threshold" ]; then
        log_warning "pytest退出码非0，但通过率达到阈值（${pass_rate_int}% >= ${threshold}%），构建标记为成功"
        return 0
    else
        log_error "测试通过率未达到阈值（${pass_rate_int}% < ${threshold}%），构建标记为失败"
        return 1
    fi
}

# 生成测试报告
generate_report() {
    log_info "生成Allure测试报告..."
    
    if [ -d "$TESTS_DIR/allure-results" ] && [ "$(ls -A $TESTS_DIR/allure-results)" ]; then
        # 创建报告目录
        mkdir -p "$TESTS_DIR/allure-reports"
        
        # 生成报告（如果有allure命令行工具）
        if command -v allure >/dev/null 2>&1; then
            allure generate "$TESTS_DIR/allure-results" -o "$TESTS_DIR/allure-reports" --clean
            log_success "Allure报告已生成到 allure-reports 目录"
        else
            log_warning "未找到allure命令行工具，跳过报告生成"
            log_info "测试结果文件位于: $TESTS_DIR/allure-results"
        fi
    else
        log_warning "未找到测试结果文件"
    fi
}

# 主函数
main() {
    log_info "开始执行自动化集成测试..."
    
    # 设置清理陷阱
    trap cleanup EXIT
    
    # 执行步骤
    check_files
    load_env_vars  # 加载环境变量
    cleanup  # 先清理可能存在的容器
    start_services
    run_tests
    generate_report

    # 按通过率阈值决定最终退出码
    if evaluate_pass_rate; then
        log_success "自动化集成测试完成（满足通过率阈值）！"
        exit 0
    else
        log_error "自动化集成测试未满足通过率阈值！"
        exit 1
    fi
}

# 执行主函数
main "$@"